#pragma once
#include <iostream>
// #include <sys/select.h>
#include <poll.h>
#include "socket.hpp"
#include "log.hpp"
#include "Inetaddr.hpp"

using namespace socket_namespace;
using namespace log_namespace;

class pollServer
{
    const static int gnum = sizeof(fd_set) * 8;
    const static int gdefaultfd = -1;

public:
    pollServer(uint16_t port)
        : _port(port), _listensock(std::make_unique<TCPSocket>())
    {
        _listensock->buildListenSocket(port);
    }

    void initServer()
    {
        for (int i = 0; i < gnum; i++)
        {
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;
        }
        // 先把listensock添加到辅助数组中
        fd_events[0].fd = _listensock->getsockfd();
        fd_events[0].events = POLLIN;
    }
    // 处理新的访问服务气器的套接字的方法
    void accepter()
    {
        // 我们叫做链接事件就绪，等同于读事件就绪(因为select只让第一个参数不为空)
        inetAddr addr;
        int ssocketfd = _listensock->Accepter(&addr);
        if (ssocketfd > 0)
        {
            int flag = 0;
            LOG(DEBUG, "get a new link, client info %s:%d\n", addr.IP().c_str(), addr.Port());
            for (int pos = 1; pos < gnum; pos++)
            {
                if (fd_events[pos].fd == gdefaultfd)
                {
                    flag = 1;
                    fd_events[pos].fd = ssocketfd;
                    fd_events[pos].events = POLLIN;
                    LOG(INFO, "add %d to fd_array success!\n", ssocketfd);
                    break;
                }
            }
            if (flag == 0)
            {
                // 走到这，说明有太多文件描述符了，
                // 此时关闭监听套接字，就能安心处理这些已经在fd_array中的文件了
                LOG(WARNING, "Server is Full!\n");
                ::close(ssocketfd);
                // 说明当前的fd_events这个结构体数组申请的空间不足，可以扩容

            }
        }
    }

    // 处理一个普通的已就绪
    void handlerIO(int i)
    {
        char buffer[1024];
        ssize_t n = ::recv(fd_events[i].fd, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            // 以下只是测试代码，并不保证每次读取到的都是一个完整的报文
            buffer[n] = 0;
            std::cout << "client say: " << buffer << std::endl;
            std::string content = "<html><body><h1>hello bit</h1></body></html>";
            std::string echo_str = "HTTP/1.0 200 OK\r\n";
            echo_str += "Content-Type: text/html\r\n";
            echo_str += "Content-Length: ";
            echo_str += std::to_string(content.size()) + "\r\n\r\n";
            echo_str += content;
            // echo_str += buffer;
            // 如果是新的文件描述符，一般都是可以往这个文件描述符中直接写的
            ::send(fd_events[i].fd, echo_str.c_str(), echo_str.size(), 0);
        }
        else if (n == 0)
        {
            LOG(INFO, "client quit...\n");
            ::close(fd_events[i].fd);
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;
        }
        else
        {
            LOG(ERROR, "recv error\n");
            ::close(fd_events[i].fd);
            fd_events[i].fd = gdefaultfd;
            fd_events[i].events = 0;
            fd_events[i].revents = 0;
        }
    }

    void handleEvent()
    {
        for (int i = 0; i < gnum; i++)
        {
            if (fd_events[i].fd == gdefaultfd)
            {
                continue;
            }
            // 走到这，也就是当前读到了一个在用的文件描述符
            if (fd_events[i].revents & POLLIN)
            {
                // 走到这，也就是找到了一个在用的文件描述符，而且这个文件描述符是读事件就绪
                if (_listensock->getsockfd() == fd_events[i].fd)
                {
                    // 走到这，也就是遍历到了listen套接字
                    accepter();
                }
                else
                {
                    // 走到这，意味着找到了一个合法的，且就绪的，且不是listen套接字的一个普通文件描述符
                    //     那么就可以对这个文件进行正常处理
                    handlerIO(i);
                }
            }
        }
    }

        void loop()
        {
            int timeout = 1000;
            while (true)
            {
                
                int n = ::poll(fd_events, gnum, timeout);
                switch (n)
                {
                case 0:
                    LOG(DEBUG, "time out\n");
                    break;

                case -1:
                    LOG(ERROR, "poll error\n");
                    break;

                default:
                    // LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec);
                    LOG(INFO, "one of events has already!, n : %d\n", n);
                    handleEvent();
                    PrintDebug();
                    // sleep(3);
                    break;
                }
            }
        }

    void PrintDebug()
    {
        std::cout << "fd list: ";
        for (int i = 0; i < gnum; i++)
        {
            if (fd_events[i].fd != gdefaultfd)
            {
                std::cout << fd_events[i].fd << " ";
            }
        }
        std::cout << "\n";
    }

    ~pollServer()
    {
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    struct pollfd fd_events[gnum];
};
